#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <strings.h>

#include "mw_cycle.h"
#include "mw_config.h"
#include "mw_core.h"
#include "mw_channel.h"
#include "mw_process.h"
#include "mw_socket.h"
#include "mw_process_cycle.h"

#include <glog/logging.h>


typedef struct {
    int     signo;
    const char   *signame;
    const char   *name;
    void  (*handler)(int signo);
} mw_signal_t;

static void mw_execute_proc(void *data);
static void mw_signal_handler(int signo);
static void mw_process_get_status(void);

int              mw_argc;
char           **mw_argv;
char           **mw_os_argv;

int              mw_process_slot;
int              mw_channel;
int              mw_last_process;
int              mw_num_coredump;
mw_process_t     mw_processes[MW_MAX_PROCESSES];


mw_signal_t  signals[] = {
    { mw_signal_value(MW_RECONFIGURE_SIGNAL),
      "SIG" mw_value(MW_RECONFIGURE_SIGNAL),
      "reload",
      mw_signal_handler },

    { mw_signal_value(MW_REOPEN_SIGNAL),
      "SIG" mw_value(MW_REOPEN_SIGNAL),
      "reopen",
      mw_signal_handler },

    { mw_signal_value(MW_NOACCEPT_SIGNAL),
      "SIG" mw_value(MW_NOACCEPT_SIGNAL),
      "",
      mw_signal_handler },

    { mw_signal_value(MW_TERMINATE_SIGNAL),
      "SIG" mw_value(MW_TERMINATE_SIGNAL),
      "stop",
      mw_signal_handler },

    { mw_signal_value(MW_SHUTDOWN_SIGNAL),
      "SIG" mw_value(MW_SHUTDOWN_SIGNAL),
      "quit",
      mw_signal_handler },

    { mw_signal_value(MW_CHANGEBIN_SIGNAL),
      "SIG" mw_value(MW_CHANGEBIN_SIGNAL),
      "",
      mw_signal_handler },

    { SIGALRM, "SIGALRM", "", mw_signal_handler },

    { SIGINT, "SIGINT", "", mw_signal_handler },

    { SIGIO, "SIGIO", "", mw_signal_handler },

    { SIGCHLD, "SIGCHLD", "", mw_signal_handler },

    { SIGSYS, "SIGSYS, SIG_IGN", "", SIG_IGN },

    { SIGPIPE, "SIGPIPE, SIG_IGN", "", SIG_IGN },

    { 0, NULL, "", NULL }
};


pid_t mw_spawn_process(mw_spawn_proc_pt proc, void *data, const char *name, int respawn)
{
    u_long      on;
    pid_t       pid;
    int         s;

    if (respawn >= 0) {
        s = respawn;
    } else {
        for (s = 0; s < mw_last_process; s++) {
            if (mw_processes[s].pid == -1) {
                break;
            }
        }

        if (s == MW_MAX_PROCESSES) {
            LOG(ERROR) << "no more than processes can be spawned";
            return MW_INVALID_PID;
        }
    }

    if (respawn != MW_PROCESS_DETACHED) {
        /* Solaris 9 still has no AF_LOCAL */

        if (socketpair(AF_UNIX, SOCK_STREAM, 0, mw_processes[s].channel) == -1) {
            LOG(ERROR) << "socketpaire() failed while spawning'" << name << "'";
            return MW_INVALID_PID;
        }


        if (mw_nonblocking(mw_processes[s].channel[0]) == -1) {
            LOG(ERROR) << "mw_nonblocking fialed while spawning '" << name << "'";
            mw_close_channel(mw_processes[s].channel);
            return MW_INVALID_PID;
        }

        if (mw_nonblocking(mw_processes[s].channel[1]) == -1) {
            LOG(ERROR) << "mw_nonblocking fialed while spawning '" << name << "'";
            mw_close_channel(mw_processes[s].channel);
            return MW_INVALID_PID;
        }

        on = 1;
        if (ioctl(mw_processes[s].channel[0], FIOASYNC, &on) == -1) {
            LOG(ERROR) << "ioctl(FIOASYNC) fialed while spawning '" << name << "'";
            mw_close_channel(mw_processes[s].channel);
            return MW_INVALID_PID;
        }

        if (fcntl(mw_processes[s].channel[0], F_SETOWN, mw_pid) == -1) {
            LOG(ERROR) << "fcntl(F_SETOWN) fialed while spawning '" << name << "'";
            mw_close_channel(mw_processes[s].channel);
            return MW_INVALID_PID;
        }

        if (fcntl(mw_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
            LOG(ERROR) << "fcntl(FD_CLOEXEC) fialed while spawning '" << name << "'";
            mw_close_channel(mw_processes[s].channel);
            return MW_INVALID_PID;
        }

        if (fcntl(mw_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
            LOG(ERROR) << "fcntl(FD_CLOEXEC) fialed while spawning '" << name << "'";
            mw_close_channel(mw_processes[s].channel);
            return MW_INVALID_PID;
        }

        mw_channel = mw_processes[s].channel[1];

    } else {
        mw_processes[s].channel[0] = -1;
        mw_processes[s].channel[1] = -1;
    }

    mw_process_slot = s;
	mw_processes[s].exiting = 0;


    pid = fork();

    switch (pid) {
        case -1:
            LOG(ERROR) << "fork() fialed while spawning '" << name << "'";
            mw_close_channel(mw_processes[s].channel);
            return MW_INVALID_PID;

        case 0:
            mw_pid = getpid();
            mw_processes[s].pid = mw_pid;
            proc(data);
            break;

        default:
            break;
    }

    LOG(INFO) << "mw_spawn_process: start " << name << " " << pid;

    mw_processes[s].pid = pid;
    mw_processes[s].exited = 0;

    if (respawn >= 0) {
        return pid;
    }

    mw_processes[s].proc = proc;
    mw_processes[s].data = data;
    mw_processes[s].name = name;
    mw_processes[s].exiting = 0;

    switch (respawn) {
        case MW_PROCESS_NORESPAWN:
            mw_processes[s].respawn = 0;
            mw_processes[s].just_spawn = 0;
            mw_processes[s].detached = 0;
            break;

        case MW_PROCESS_JUST_SPAWN:
            mw_processes[s].respawn = 0;
            mw_processes[s].just_spawn = 1;
            mw_processes[s].detached = 0;
            break;

        case MW_PROCESS_RESPAWN:
            mw_processes[s].respawn = 1;
            mw_processes[s].just_spawn = 0;
            mw_processes[s].detached = 0;
            break;

        case MW_PROCESS_JUST_RESPAWN:
            mw_processes[s].respawn = 1;
            mw_processes[s].just_spawn = 1;
            mw_processes[s].detached = 0;
            break;

        case MW_PROCESS_DETACHED:
            mw_processes[s].respawn = 0;
            mw_processes[s].just_spawn = 0;
            mw_processes[s].detached = 1;
            break;
    }

    if (s == mw_last_process) {
        mw_last_process++;
    }

    return pid;
}


pid_t mw_execute(mw_exec_ctx_t *ctx) {
    return mw_spawn_process(mw_execute_proc, ctx, ctx->name, MW_PROCESS_DETACHED);
}


static void mw_execute_proc(void *data) {
    mw_exec_ctx_t *ctx = (mw_exec_ctx_t*)data;

    if (execve(ctx->path, ctx->argv, ctx->envp) == -1) {
        LOG(ERROR) << "execve() failed while executing " << ctx->name
            << " '" << ctx->path << "'";
    }

    exit(1);
}


int mw_init_signals()
{
    mw_signal_t      *sig;
    struct sigaction   sa;

    for (sig = signals; sig->signo != 0; sig++) {
        bzero(&sa, sizeof(struct sigaction));
        sa.sa_handler = sig->handler;
        sigemptyset(&sa.sa_mask);

        if (sigaction(sig->signo, &sa, NULL) == -1) {
            LOG(ERROR) << "mw_init_signals: sigaction(" << sig->signame << ") failed";
            return MW_ERROR;
        }
    }

    return MW_OK;
}


void mw_signal_handler(int signo)
{
    const char            *action;
    int              ignore;
    int              err;
    mw_signal_t    *sig;

    ignore = 0;

    err = errno;

    for (sig = signals; sig->signo != 0; sig++) {
        if (sig->signo == signo) {
            break;
        }
    }

    action = "";

    switch (mw_process) {
        case MW_PROCESS_MASTER:
        case MW_PROCESS_SINGLE:
            switch (signo) {
                case mw_signal_value(MW_SHUTDOWN_SIGNAL):
                    mw_quit = 1;
                    action = ", shutting down";
                    break;

                case mw_signal_value(MW_TERMINATE_SIGNAL):
                case SIGINT:
                    mw_terminate = 1;
                    action = ", exiting";
                    break;

                case mw_signal_value(MW_NOACCEPT_SIGNAL):
                    if (mw_daemonized) {
                        mw_noaccept = 1;
                        action = ", stop accepting connections";
                    }
                    break;

                case mw_signal_value(MW_RECONFIGURE_SIGNAL):
                    mw_reconfigure = 1;
                    action = ", reconfiguring";
                    break;

                case mw_signal_value(MW_REOPEN_SIGNAL):
                    mw_reopen = 1;
                    action = ", reopening logs";
                    break;

                case mw_signal_value(MW_CHANGEBIN_SIGNAL):
                    if (getppid() > 1 || mw_new_binary > 0) {
                        /*
                         * Ignore the signal in the new binary if its parent is
                         * not the init process, i.e. the old binary's process
                         * is still running.  Or ignore the signal in the old binary's
                         * process if the new binary's process is already running.
                         */

                        action = ", ignoring";
                        ignore = 1;
                        break;
                    }

                    mw_change_binary = 1;
                    action = ", changing binary";
                    break;

                case SIGALRM:
                    mw_sigalrm = 1;
                    break;

                case SIGIO:
                    mw_sigio = 1;
                    break;

                case SIGCHLD:
                    mw_reap = 1;
					LOG(INFO)<<"case SIGCHLD:";
                    break;
            }

            break;

        case MW_PROCESS_WORKER:
        case MW_PROCESS_HELPER:
            switch (signo) {
                case mw_signal_value(MW_NOACCEPT_SIGNAL):
                    if (!mw_daemonized) {
                        break;
                    }
                    mw_debug_quit = 1;
                case mw_signal_value(MW_SHUTDOWN_SIGNAL):
                    mw_quit = 1;
                    action = ", shutting down";
                    break;

                case mw_signal_value(MW_TERMINATE_SIGNAL):
                case SIGINT:
                    mw_terminate = 1;
                    action = ", exiting";
                    break;

                case mw_signal_value(MW_REOPEN_SIGNAL):
                    mw_reopen = 1;
                    action = ", reopening logs";
                    break;

                case mw_signal_value(MW_RECONFIGURE_SIGNAL):
                case mw_signal_value(MW_CHANGEBIN_SIGNAL):
                case SIGIO:
                    action = ", ignoring";
                    break;
            }

            break;
    }

#if 0
    logging_debug << "mw_signal_handler: singal " << signo << "(" << sig->signame << ") received" << action;

    if (ignore) 
    {
        logging_debug << "the changing binary signal is ignored: "
                     "you should shutdown or terminate "
                     "before either old or new binary's process";
    }
#endif

    if (signo == SIGCHLD) {
        mw_process_get_status();
    }

    errno = err;
}


static void mw_process_get_status(void)
{
    int              status;
    const char            *process;
    pid_t            pid;
    int              err;
    int              i;
    uint32_t         one;

    one = 0;

    for ( ;; ) {
        pid = waitpid(-1, &status, WNOHANG);

        if (pid == 0) {
            return;
        }

        if (pid == -1) {
            err = errno;

            if (err == EINTR) {
                continue;
            }

            if (err == ECHILD && one) {
                return;
            }

            return;
        }


        one = 1;
        process = "unknown process";

        for (i = 0; i < mw_last_process; i++) {
            if (mw_processes[i].pid == pid) {
                mw_processes[i].status = status;
                mw_processes[i].exited = 1;
                process = mw_processes[i].name;
                break;
            }
        }

        if (WTERMSIG(status)) {
#ifdef WCOREDUMP
            /*
            LOG(ERROR) << __func__ << ": " << process << " " << pid 
                       << " exited on signal " << WTERMSIG(status) 
                       << (WCOREDUMP(status) ? " (core dumped)" : "");
            */
            if (WCOREDUMP(status)){
                mw_num_coredump++;
			}
#else

            /*
            LOG(ERROR) << __func__ << ": " << process << "" << pid
                       << " exited on signal " << WTERMSIG(status);
            */
#endif

        } else {
            /*
            LOG(ERROR) << __func__ << ": " << process << " " << pid
                       << " exited with code " << WEXITSTATUS(status);
            */
        }

        if (WEXITSTATUS(status) == 2 && mw_processes[i].respawn) {
            /* 
            LOG(ERROR) << __func__ << ": " << process << " " << pid
                       << " exited with fatal code " << WEXITSTATUS(status) << " and connot be respawned";
            */
            mw_processes[i].respawn = 0;
        }
    }
}


void mw_debug_point(void)
{
    int debug_points = MW_DEBUG_POINTS_STOP;

    switch (debug_points) {

    case MW_DEBUG_POINTS_STOP:
        raise(SIGSTOP);
        break;

    case MW_DEBUG_POINTS_ABORT:
        abort();
    }
}


int mw_os_signal_process(char *name, int pid)
{
    mw_signal_t  *sig;

    for (sig = signals; sig->signo != 0; sig++) {
        if (strcmp(name, sig->name) == 0) {
            if (kill(pid, sig->signo) != -1) {
                return 0;
            }

            LOG(ERROR) << "mw_os_signal_process: kill(" << pid
                << "," << sig->signo << ") failed";
        }
    }

    return 1;
}
